using System;
using System.Collections.Generic;
using System.Text;

using Weazel.Microblaze.Simulator.Engine;

namespace Weazel.Microblaze.Simulator.Components.Microblaze
{
    public static class Decoder
    {
        private static InstructionSet instructionSet = new InstructionSet();
        public static InstructionSet InstructionSet
        {
            get { return Decoder.instructionSet; }
            set { Decoder.instructionSet = value; }
        }

        private static bool debug = true;

        private static uint destinationRegister;
        private static uint sourceRegisterA;
        private static uint sourceRegisterB;
        private static int immediateValue;

        public static Instruction Decode(uint address, uint value)
        {
            uint opcode = value >> 26;

            // extract destination register
            destinationRegister = (value >> 21) & 0x1F;

            // extract source register A
            sourceRegisterA = (value >> 16) & 0x1F;

            // extract source register B
            sourceRegisterB = (value >> 11) & 0x1F;

            // .. and the immediate value
            immediateValue =
              (int)((((int)value & 0xFFFF) << 16) >> 16);

            if (value == 0)
            {
                instructionSet.Nop.Set(address, value);
                return instructionSet.Nop;                
            }

            if ((opcode & 0x39) == 0x00)
            {
                // add
                instructionSet.Add.Set(address, value);
                return instructionSet.Add;
            }
            else if ((opcode & 0x39) == 8)
            {
                // addi
                instructionSet.Addi.Set(address, value);
                return instructionSet.Addi;
            }
            else if (opcode == 0x21)
            {
                instructionSet.And.Set(address, value);
                return instructionSet.And;
            }
            else if (opcode == 0x29)
            {
                instructionSet.Andi.Set(address, value);
                return instructionSet.Andi;
            }
            else if (opcode == 0x23)
            {
                instructionSet.Andn.Set(address, value);
                return instructionSet.Andn;
            }
            else if (opcode == 0x2B)
            {
                instructionSet.Andni.Set(address, value);
                return instructionSet.Andni;
            }
            else if (opcode == 27)
            {
                uint subOpcode = destinationRegister & 0xF;

                if (subOpcode == 0x0)
                {
                    instructionSet.Beq.Set(address, value);
                    return instructionSet.Beq;
                }
                else if (subOpcode == 0x1)
                {
                    instructionSet.Bne.Set(address, value);
                    return instructionSet.Bne;
                }
                else if (subOpcode == 0x2)
                {
                    instructionSet.Blt.Set(address, value);
                    return instructionSet.Blt;
                }
                else if (subOpcode == 0x3)
                {
                    instructionSet.Ble.Set(address, value);
                    return instructionSet.Ble;
                }
                else if (subOpcode == 0x4)
                {
                    instructionSet.Bgt.Set(address, value);
                    return instructionSet.Bgt;
                }
                else if (subOpcode == 0x5)
                {
                    instructionSet.Bge.Set(address, value);
                    return instructionSet.Bge;
                }
                else
                {
                    instructionSet.Error.Set(address, value);
                    return instructionSet.Error;
                }
            }
            else if (opcode == 0x2F)
            {
                if ((destinationRegister & 0xF) == 0x00)
                {
                    instructionSet.Beqi.Set(address, value);
                    return instructionSet.Beqi;
                }
                else if ((destinationRegister & 0xF) == 0x05)
                {
                    instructionSet.Bgei.Set(address, value);
                    return instructionSet.Bgei;
                }
                else if ((destinationRegister & 0xF) == 0x04)
                {
                    instructionSet.Bgti.Set(address, value);
                    return instructionSet.Bgti;
                }
                else if ((destinationRegister & 0xF) == 0x03)
                {
                    instructionSet.Blei.Set(address, value);
                    return instructionSet.Blei;
                }
                else if ((destinationRegister & 0xF) == 0x02)
                {
                    instructionSet.Blti.Set(address, value);
                    return instructionSet.Blti;
                }
                else if ((destinationRegister & 0xF) == 0x01)
                {
                    instructionSet.Bnei.Set(address, value);
                    return instructionSet.Bnei;
                }
                else
                {
                    instructionSet.Error.Set(address, value);
                    return instructionSet.Error;
                }
            }
            else if (opcode == 0x26)
            {
                if (sourceRegisterB == 0x0C)
                {
                    instructionSet.Brk.Set(address, value);
                    return instructionSet.Brk;
                }
                else
                {
                    instructionSet.Br.Set(address, value);
                    return instructionSet.Br;
                }
            }
            else if (opcode == 0x2E)
            {
                if (sourceRegisterA == 0x0C)
                {
                    instructionSet.Brki.Set(address, value);
                    return instructionSet.Brki;
                }
                else
                {
                    instructionSet.Bri.Set(address, value);
                    return instructionSet.Bri;
                }
            }
            else if (opcode == 0x11)
            {
                instructionSet.Bs.Set(address, value);
                return instructionSet.Bs;
            }
            else if (opcode == 0x19)
            {
                instructionSet.Bsi.Set(address, value);
                return instructionSet.Bsi;
            }
            else if (opcode == 0x16)
            {
                if (debug)
                    Console.WriteLine("fadd OR frsub OR fmul OR fdiv OR fcmp");
                // fadd OR frsub OR fmul OR fdiv OR fcmp

                instructionSet.Error.Set(address, value);
                return instructionSet.Error;
            }
            else if (opcode == 0x1B)
            {
                instructionSet.Get.Set(address, value);
                return instructionSet.Get;
            }
            else if (opcode == 0x12)
            {
                instructionSet.Idiv.Set(address, value);
                return instructionSet.Idiv;
            }
            else if (opcode == 0x2C)
            {
                instructionSet.Imm.Set(address, value);
                return instructionSet.Imm;
            }
            else if (opcode == 0x30)
            {
                instructionSet.Lbu.Set(address, value);
                return instructionSet.Lbu;
            }
            else if (opcode == 0x38)
            {
                instructionSet.Lbui.Set(address, value);
                return instructionSet.Lbui;
            }
            else if (opcode == 0x31)
            {
                instructionSet.Lhu.Set(address, value);
                return instructionSet.Lhu;
            }
            else if (opcode == 0x39)
            {
                instructionSet.Lhui.Set(address, value);
                return instructionSet.Lhui;
            }
            else if (opcode == 0x32)
            {
                instructionSet.Lw.Set(address, value);
                return instructionSet.Lw;
            }
            else if (opcode == 0x3A)
            {
                instructionSet.Lwi.Set(address, value);
                return instructionSet.Lwi;
            }
            else if (opcode == 0x25)
            {
                uint sub_value = (value >> 14) & 0x03;

                if (sub_value == 0x3)
                {
                    instructionSet.Mts.Set(address, value);
                    return instructionSet.Mts;
                }
                else if (sub_value == 0x2)
                {
                    instructionSet.Mfs.Set(address, value);
                    return instructionSet.Mfs;
                }
                else if (sourceRegisterA == 1)
                {
                    instructionSet.Msrclr.Set(address, value);
                    return instructionSet.Msrclr;
                }
                else if (sourceRegisterA == 0)
                {
                    instructionSet.Msrset.Set(address, value);
                    return instructionSet.Msrset;
                }
                else
                {
                    instructionSet.Error.Set(address, value);
                    return instructionSet.Error;
                }
            }
            else if (opcode == 0x10)
            {
                instructionSet.Mul.Set(address, value);
                return instructionSet.Mul;
            }
            else if (opcode == 0x18)
            {
                instructionSet.Muli.Set(address, value);
                return instructionSet.Muli;
            }
            else if (opcode == 0x20)
            {
                if (((value >> 10) & 1) == 0) // 10 least significant bits all 0
                {
                    instructionSet.Or.Set(address, value);
                    return instructionSet.Or;
                }
                else if (((value >> 10) & 1) == 1) // msb of the ls 10 bits set
                {
                    instructionSet.Pcmpbf.Set(address, value);
                    return instructionSet.Pcmpbf;
                }
                else
                {
                    throw new UnknownInstructionException(value);
                }
            }
            else if (opcode == 0x28)
            {
                instructionSet.Ori.Set(address, value);
                return instructionSet.Ori;
            }
            else if (opcode == 0x22)
            {
                if (((value >> 10) & 1) == 0)
                {
                    instructionSet.Xor.Set(address, value);
                    return instructionSet.Xor;
                }
                else
                {
                    instructionSet.Pcmpeq.Set(address, value);
                    return instructionSet.Pcmpeq;
                }
            }
            else if (opcode == 0x23)
            {
                instructionSet.Pcmpne.Set(address, value);
                return instructionSet.Pcmpne;
            }
            else if (opcode == 0x1B)
            {
                instructionSet.Put.Set(address, value);
                return instructionSet.Put;
            }
            else if ((opcode & 0x39) == 0x01)
            {
                // can be either rsub or cmp

                if ((value & 0x01) == 0x00)
                {
                    instructionSet.Rsub.Set(address, value);
                    return instructionSet.Rsub;
                }
                else
                {
                    instructionSet.Cmp.Set(address, value);
                    return instructionSet.Cmp;
                }
            }
            else if ((opcode & 0x39) == 0x09)
            {
                instructionSet.Rsubi.Set(address, value);
                return instructionSet.Rsubi;
            }
            else if (opcode == 0x2D)
            {
                if (destinationRegister == 0x12)
                {
                    instructionSet.Rtbd.Set(address, value);                    
                    return instructionSet.Rtbd;
                }
                else if (destinationRegister == 0x11)
                {
                    instructionSet.Rtid.Set(address, value);
                    return instructionSet.Rtid;
                }
                else if (destinationRegister == 0x12)
                {
                    // NOTE: same instruction as rtbd???
                    instructionSet.Rted.Set(address, value);
                    return instructionSet.Rted;
                }
                else if (destinationRegister == 0x10)
                {
                    instructionSet.Rtsd.Set(address, value);
                    return instructionSet.Rtsd;
                }
                else
                {
                    instructionSet.Error.Set(address, value);
                    return instructionSet.Error;
                }
            }
            else if (opcode == 0x34)
            {
                instructionSet.Sb.Set(address, value);
                return instructionSet.Sb;
            }
            else if (opcode == 0x3C)
            {
                instructionSet.Sbi.Set(address, value);
                return instructionSet.Sbi;
            }
            else if (opcode == 0x24)
            {
                if (immediateValue == 0x01)
                {
                    instructionSet.Sra.Set(address, value);
                    return instructionSet.Sra;
                }
                else if (immediateValue == 0x21)
                {
                    instructionSet.Src.Set(address, value);
                    return instructionSet.Src;
                }
                else if (immediateValue == 0x41)
                {
                    instructionSet.Srl.Set(address, value);
                    return instructionSet.Srl;
                }
                else if (immediateValue == 0x61)
                {
                    instructionSet.Sext16.Set(address, value);
                    return instructionSet.Sext16;
                }
                else if (immediateValue == 0x60)
                {
                    instructionSet.Sext8.Set(address, value);
                    return instructionSet.Sext8;
                }
                else if (immediateValue == 0x64)
                {
                    instructionSet.Wdc.Set(address, value);
                    return instructionSet.Wdc;
                }
                else if (immediateValue == 0x68)
                {
                    instructionSet.Wic.Set(address, value);
                    return instructionSet.Wic;
                }
                else
                {
                    instructionSet.Error.Set(address, value);
                    return instructionSet.Error;
                }
            }
            else if (opcode == 0x35)
            {
                instructionSet.Sh.Set(address, value);
                return instructionSet.Sh;
            }
            else if (opcode == 0x3D)
            {
                instructionSet.Shi.Set(address, value);
                return instructionSet.Shi;
            }
            else if (opcode == 0x36)
            {
                instructionSet.Sw.Set(address, value);
                return instructionSet.Sw;
            }
            else if (opcode == 0x3E)
            {
                instructionSet.Swi.Set(address, value);
                return instructionSet.Swi;
            }
            else if (opcode == 0x22)
            {
                instructionSet.Xor.Set(address, value);
                return instructionSet.Xor;
            }
            else if (opcode == 0x2A)
            {
                instructionSet.Xori.Set(address, value);
                return instructionSet.Xori;
            }
            else
            {
                instructionSet.Error.Set(address, value);
                return instructionSet.Error;
            }
        }
    }
}
